﻿
Imports std

Public Class value
    Inherits iScript
    Implements iInvokeableWithOutput

    Public Const surroundingLeft As Char = character.leftBracket
    Public Const surroundingRight As Char = character.rightBracket
    Public Const no_ignore_bit As Int64 = 0
    Public Const ignore_value_bit As Int64 = 1
    Public Const ignore_bare_variable_bit As Int64 = 2
    Public Const ignore_function_call_bit As Int64 = 4
    Public Const ignore_function_call2_bit As Int64 = 8
    Public Const ignore_function_call3_bit As Int64 = 16
    Public Const ignore_function_call4_bit As Int64 = 32

    Private Shared Function _ignore(ByVal bit As Int64, Optional ByVal surrounding As Int64 = 0) As Boolean
        Return surrounding = 0 AndAlso ((current_ignore().ignore() And bit) > 0)
    End Function

    Private Shared Function ignore_value(ByVal surrounding As Int64) As Boolean
        Return _ignore(ignore_value_bit, surrounding)
    End Function

    Private Shared Function ignore_bare_variable(ByVal surrounding As Int64) As Boolean
        Return _ignore(ignore_bare_variable_bit, surrounding)
    End Function

    Private Shared Function ignore_function_call(ByVal surrounding As Int64) As Boolean
        Return _ignore(ignore_function_call_bit, surrounding)
    End Function

    Private Shared Function ignore_function_call2(ByVal surrounding As Int64) As Boolean
        Return _ignore(ignore_function_call2_bit, surrounding)
    End Function

    Private Shared Function ignore_function_call3(ByVal surrounding As Int64) As Boolean
        Return _ignore(ignore_function_call3_bit, surrounding)
    End Function

    Private Shared Function ignore_function_call4(ByVal surrounding As Int64) As Boolean
        Return _ignore(ignore_function_call4_bit, surrounding)
    End Function

    Protected Friend Shared Function _isMeaningfulMark(ByVal c As Char) As Boolean
        Return isSurroundingLeft(c) OrElse isSurroundingRight(c)
    End Function

    Private value_name As String = Nothing
    Private bare_variable As bare_variable = Nothing
    Private function_call As function_call = Nothing
    Private function_call2 As function_call2 = Nothing
    Private function_call3 As function_call3 = Nothing
    Private function_call4 As function_call4 = Nothing
    <ThreadStatic()> Private Shared ignore_stack As stack(Of ignore) = Nothing

    Private Class ignore
        Public i As Int64
        Public s As stack(Of Int64)

        Public Sub New()
            i = no_ignore_bit
            s = New stack(Of Int64)()
        End Sub

        Public Sub set_ignore(ByVal input As Int64)
            assert(input <> 0)
            'in surrounding > 0, another set_ignore may be called, so push a 0 to avoid to break the stack
            If _ignore(input) Then
                s.push(no_ignore_bit)
            Else
                i = i Or input
                s.push(input)
            End If
        End Sub

        Public Sub clear_ignore()
            assert(Not s Is Nothing)
            assert(s.size() > 0)
            Dim b As Int64 = 0
            b = s.back()
            s.pop()
            assert(b = no_ignore_bit OrElse _ignore(b))
            If b <> no_ignore_bit Then
                i = i Xor b
            End If
        End Sub

        Public Function ignore() As Int64
            Return i
        End Function

        Public Function empty() As Boolean
            Return i = no_ignore_bit AndAlso s.empty()
        End Function
    End Class

    Private Shared Sub create_ignore_stack()
        If ignore_stack Is Nothing Then
            ignore_stack = New stack(Of ignore)()
        End If
    End Sub

    Private Shared Function current_ignore(Optional ByVal existing As Boolean = True) As ignore
        assert(Not ignore_stack Is Nothing)
        If existing Then
            assert(ignore_stack.size() > 0)
        Else
            If ignore_stack.size() = 0 Then
                _push_ignore()
            End If
        End If
        Return ignore_stack.back()
    End Function

    Private Shared Sub set_ignore(ByVal i As Int64)
        assert(i <> 0)
        create_ignore_stack()
        current_ignore(False).set_ignore(i)
    End Sub

    Private Shared Sub clear_ignore()
        current_ignore().clear_ignore()
    End Sub

    Private Shared Sub _push_ignore()
        ignore_stack.push(New ignore())
    End Sub

    Private Shared Sub push_ignore()
        create_ignore_stack()
        _push_ignore()
    End Sub

    Private Shared Sub pop_ignore()
        assert(Not ignore_stack Is Nothing)
        assert(current_ignore().empty())
        ignore_stack.pop()
    End Sub

    Public Shared Sub assert_ignore()
        assert(ignore_stack Is Nothing OrElse ignore_stack.size() <= 1)
        If Not ignore_stack Is Nothing AndAlso ignore_stack.size() = 1 Then
            assert(current_ignore().empty())
        End If
    End Sub

    Private Sub New(Optional ByVal ivalue_name As String = Nothing, _
                    Optional ByVal ibare_variable As bare_variable = Nothing, _
                    Optional ByVal ifunction_call As function_call = Nothing, _
                    Optional ByVal ifunction_call2 As function_call2 = Nothing, _
                    Optional ByVal ifunction_call3 As function_call3 = Nothing, _
                    Optional ByVal ifunction_call4 As function_call4 = Nothing)
        value_name = ivalue_name
        bare_variable = ibare_variable
        function_call = ifunction_call
        function_call2 = ifunction_call2
        function_call3 = ifunction_call3
        function_call4 = ifunction_call4
    End Sub

    Protected Overrides Function invokeImpl(ByVal ctx As context, ByRef output As bare_variable) As Boolean
        If Not value_name Is Nothing Then
            Dim v As bare_variable = Nothing
            v = ctx.findVariable(value_name)
            If Not v Is Nothing Then
                copy(output, v)
                Return True
            Else
                raiseError("expect value_name " + value_name, errorHandle.errorType.user)
                Return False
            End If
        End If

        If Not bare_variable Is Nothing Then
            Return copy(output, bare_variable)
        End If

        If Not function_call Is Nothing Then
            Return function_call.invoke(ctx, output)
        End If

        If Not function_call2 Is Nothing Then
            Return function_call2.invoke(ctx, output)
        End If

        If Not function_call3 Is Nothing Then
            Return function_call3.invoke(ctx, output)
        End If

        If Not function_call4 Is Nothing Then
            Return function_call4.invoke(ctx, output)
        End If

        Return False
    End Function

    Public Function invoke(ByVal ctx As context, ByRef output As bare_variable) As Boolean _
                                                                                Implements iInvokeableWithOutput.invoke
        Return invokebase(ctx, output)
    End Function

    Private Shared Function isSurroundingLeft(ByVal c As Char) As Boolean
        Return c = surroundingLeft
    End Function

    Private Shared Function isSurroundingRight(ByVal c As Char) As Boolean
        Return c = surroundingRight
    End Function

    Private Shared Function parseValue(ByVal input As String, ByRef index As Int64, ByRef output As value, _
                                       ByVal surrounding As Int64) As Boolean
        'if more different function_calls later, should try to parse them one by one, and try the surrounding,
        'instead of increase surrounding after one path failed.
        If Not ignore_function_call3(surrounding) Then
            Dim f3 As function_call3 = Nothing
            If function_call3.parse(input, index, f3) Then
                output = New value(ifunction_call3:=f3)
                Return True
            End If
        End If

        If Not ignore_function_call(surrounding) Then
            Dim f As function_call = Nothing
            If function_call.parse(input, index, f) Then
                output = New value(ifunction_call:=f)
                Return True
            End If
        End If

        If Not ignore_function_call2(surrounding) Then
            Dim f2 As function_call2 = Nothing
            If function_call2.parse(input, index, f2) Then
                output = New value(ifunction_call2:=f2)
                Return True
            End If
        End If

        If Not ignore_function_call4(surrounding) Then
            Dim f4 As function_call4 = Nothing
            If function_call4.parse(input, index, f4) Then
                output = New value(ifunction_call4:=f4)
                Return True
            End If
        End If

        If Not ignore_bare_variable(surrounding) Then
            Dim v As bare_variable = Nothing
            If bare_variable.parse(input, index, v) Then
                output = New value(ibare_variable:=v)
                Return True
            End If
        End If

        If Not ignore_value(surrounding) Then
            Dim name As String = Nothing
            If value_name_defination.parse(input, index, name) Then
                output = New value(ivalue_name:=name)
                Return True
            End If
        End If

        Return False
    End Function

    Private Shared Function parseImpl(ByVal input As String, ByRef index As Int64, ByRef output As value) As Boolean
        Dim surrounding As Int64
        surrounding = 0
        Dim pv As parseImplDelegate(Of value) = _
            Function(_input As String, ByRef _index As Int64, ByRef _output As value) As Boolean
                Return parseValue(_input, _index, _output, surrounding)
            End Function
        Do
            Dim lastIndex As Int64
            lastIndex = index
            If parsebase(input, index, output, pv, False) Then
                Dim i As Int64
                For i = 0 To surrounding - 1
                    If Not token(input, index, Nothing, AddressOf isSurroundingRight, AddressOf [false]) Then
                        Exit For
                    End If
                Next
                If i = surrounding Then
                    Return True
                Else
                    index = lastIndex
                    output = Nothing
                End If
            End If
            inc(surrounding)
        Loop Until Not token(input, index, Nothing, AddressOf isSurroundingLeft, AddressOf [false])

        Return False
    End Function

    Private Shared Function parse_final(ByVal enableIgnore As Boolean) As Boolean
        If enableIgnore Then
            clear_ignore()
        Else
            pop_ignore()
        End If
        Return True
    End Function

    Public Shared Function parse(ByVal input As String, ByRef index As Int64, ByRef output As value, _
                                 Optional ByVal _ignore As Int64 = no_ignore_bit) As Boolean
        Dim enableIgnore As Boolean
        enableIgnore = (_ignore <> no_ignore_bit)
        If enableIgnore Then
            set_ignore(_ignore)
        Else
            push_ignore()
        End If
        'always clear_ignore even parsefailed.
        Return parsebase(input, index, output, AddressOf parseImpl) And parse_final(enableIgnore)
    End Function

    Public Overrides Function ToString() As String
        Return scriptSource()
    End Function
End Class
